Fedezze fel a párhuzamos adatlekérés fejlett technikáit a React-ben a Suspense használatával, javítva az alkalmazás teljesítményét és a felhasználói élményt.
React Suspense koordináció: A párhuzamos adatlekérés elsajátítása
A React Suspense forradalmasította az aszinkron műveletek, különösen az adatlekérés kezelésének módját. Lehetővé teszi a komponensek számára, hogy "felfüggesszék" a renderelést, miközben adat betöltésére várnak, deklaratív módot biztosítva a betöltési állapotok kezelésére. Azonban az egyes adatlekérések egyszerűen Suspense-szel történő becsomagolása vízesés hatást eredményezhet, ahol az egyik lekérés befejeződik, mielőtt a következő elindulna, ami negatívan befolyásolja a teljesítményt. Ez a blogbejegyzés a párhuzamosan több adatlekérés koordinálásának fejlett stratégiáiba mélyül bele a Suspense használatával, optimalizálva alkalmazásának reakcióképességét és javítva a felhasználói élményt a globális közönség számára.
A vízesés probléma megértése az adatlekérésben
Képzeljen el egy olyan forgatókönyvet, ahol egy felhasználói profilt szeretne megjeleníteni a nevével, avatarjával és a legutóbbi tevékenységével. Ha az adatokat sorrendben lekéri, a felhasználó egy betöltőforgót lát a névhez, majd egy másikat az avatarhoz, végül egyet a tevékenységi hírfolyamhoz. Ez a szekvenciális betöltési minta vízesés hatást hoz létre, késleltetve a teljes profil renderelését és frusztrálva a felhasználókat. A nemzetközi felhasználók számára, akik változó hálózati sebességgel rendelkeznek, ez a késés még hangsúlyosabb lehet.
Tekintse meg ezt az egyszerűsített kódkivonatot:
function UserProfile() {
const name = useName(); // Felhasználónév lekérése
const avatar = useAvatar(name); // Avatar lekérése a név alapján
const activity = useActivity(name); // Tevékenység lekérése a név alapján
return (
<div>
<h2>{name}</h2>
<img src={avatar} alt="Felhasználó avatar" />
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
</div>
);
}
Ebben a példában a useAvatar és a useActivity a useName eredményétől függ. Ez egyértelmű vízesést hoz létre – a useAvatar és a useActivity nem tudja elkezdeni az adatok lekérését, amíg a useName be nem fejeződik. Ez nem hatékony és gyakori teljesítmény szűk keresztmetszet.
Stratégiák a párhuzamos adatlekéréshez a Suspense segítségével
A Suspense használatával történő adatlekérés optimalizálásának kulcsa az összes adatigény egyidejű kezdeményezése. Íme néhány stratégia, amelyet alkalmazhat:
1. Adatok előtöltése a `React.preload` és az erőforrások segítségével
Az egyik leghatékonyabb technika az adatok előzetes betöltése, még mielőtt a komponens lefutna. Ez egy "erőforrás" (egy objektum, amely az adatlekérési ígéretet foglalja magában) létrehozását és az adatok előzetes lekérését foglalja magában. A `React.preload` ehhez segít. Mire a komponensnek szüksége van az adatokra, azok már elérhetőek, szinte teljesen megszüntetve a betöltési állapotot.
Vegyünk egy erőforrást egy termék lekéréséhez:
const createProductResource = (productId) => {
let promise;
let product;
let error;
const suspender = new Promise((resolve, reject) => {
promise = fetch(`/api/products/${productId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP hiba! Állapot: ${response.status}`);
}
return response.json();
})
.then(data => {
product = data;
resolve();
})
.catch(e => {
error = e;
reject(e);
});
});
return {
read() {
if (error) {
throw error;
}
if (product) {
return product;
}
throw suspender;
},
};
};
// Használat:
const productResource = createProductResource(123);
function ProductDetails() {
const product = productResource.read();
return (<div>{product.name}</div>);
}
Most előtöltheti ezt az erőforrást, mielőtt a ProductDetails komponens lefut. Például útvonalváltások során vagy egérrel való rámutatáskor.
React.preload(productResource);
Ez biztosítja, hogy az adatok nagy valószínűséggel rendelkezésre állnak, mire a ProductDetails komponensnek szüksége lesz rájuk, minimalizálva vagy megszüntetve a betöltési állapotot.
2. A `Promise.all` használata az egyidejű adatlekéréshez
Egy másik egyszerű és hatékony megközelítés a Promise.all használata az összes adatlekérés egyidejű kezdeményezéséhez egyetlen Suspense határon belül. Ez jól működik, ha az adatfüggőségek előre ismertek.
Vizsgáljuk felül a felhasználói profil példát. Ahelyett, hogy sorban lekérnénk az adatokat, egyidejűleg lekérhetjük a nevet, az avatart és a tevékenységi hírfolyamot:
import { useState, useEffect, Suspense } from 'react';
async function fetchName() {
// API-hívás szimulálása
await new Promise(resolve => setTimeout(resolve, 500));
return 'John Doe';
}
async function fetchAvatar(name) {
// API-hívás szimulálása
await new Promise(resolve => setTimeout(resolve, 300));
return `https://example.com/avatars/${name.toLowerCase().replace(' ', '-')}.jpg`;
}
async function fetchActivity(name) {
// API-hívás szimulálása
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, text: 'Feltöltött egy fotót' },
{ id: 2, text: 'Frissítette a profilt' },
];
}
function useSuspense(promise) {
const [result, setResult] = useState(null);
useEffect(() => {
let didCancel = false;
promise.then(
(data) => {
if (!didCancel) {
setResult({ status: 'success', value: data });
}
},
(error) => {
if (!didCancel) {
setResult({ status: 'error', value: error });
}
}
);
return () => {
didCancel = true;
};
}, [promise]);
if (result?.status === 'success') {
return result.value;
} else if (result?.status === 'error') {
throw result.value;
} else {
throw promise;
}
}
function Name() {
const name = useSuspense(fetchName());
return <h2>{name}</h2>;
}
function Avatar({ name }) {
const avatar = useSuspense(fetchAvatar(name));
return <img src={avatar} alt="Felhasználó avatar" />;
}
function Activity({ name }) {
const activity = useSuspense(fetchActivity(name));
return (
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
function UserProfile() {
const name = useSuspense(fetchName());
return (
<div>
<Suspense fallback=<div>Avatar betöltése...</div>>
<Avatar name={name} />
</Suspense>
<Suspense fallback=<div>Tevékenység betöltése...</div>>
<Activity name={name} />
</Suspense>
</div>
);
}
export default UserProfile;
Ha azonban az `Avatar` és az `Activity` is a `fetchName`-től függ, de külön Suspense határokon belül jelenik meg, akkor a `fetchName` ígéretet a szülőbe emelheti, és a React Context-en keresztül adhatja meg.
import React, { createContext, useContext, useState, useEffect, Suspense } from 'react';
async function fetchName() {
// API-hívás szimulálása
await new Promise(resolve => setTimeout(resolve, 500));
return 'John Doe';
}
async function fetchAvatar(name) {
// API-hívás szimulálása
await new Promise(resolve => setTimeout(resolve, 300));
return `https://example.com/avatars/${name.toLowerCase().replace(' ', '-')}.jpg`;
}
async function fetchActivity(name) {
// API-hívás szimulálása
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, text: 'Feltöltött egy fotót' },
{ id: 2, text: 'Frissítette a profilt' },
];
}
function useSuspense(promise) {
const [result, setResult] = useState(null);
useEffect(() => {
let didCancel = false;
promise.then(
(data) => {
if (!didCancel) {
setResult({ status: 'success', value: data });
}
},
(error) => {
if (!didCancel) {
setResult({ status: 'error', value: error });
}
}
);
return () => {
didCancel = true;
};
}, [promise]);
if (result?.status === 'success') {
return result.value;
} else if (result?.status === 'error') {
throw result.value;
} else {
throw promise;
}
}
const NamePromiseContext = createContext(null);
function Avatar() {
const namePromise = useContext(NamePromiseContext);
const name = useSuspense(namePromise);
const avatar = useSuspense(fetchAvatar(name));
return <img src={avatar} alt="Felhasználó avatar" />;
}
function Activity() {
const namePromise = useContext(NamePromiseContext);
const name = useSuspense(namePromise);
const activity = useSuspense(fetchActivity(name));
return (
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
function UserProfile() {
const namePromise = fetchName();
return (
<NamePromiseContext.Provider value={namePromise}>
<Suspense fallback=<div>Avatar betöltése...</div>>
<Avatar />
</Suspense>
<Suspense fallback=<div>Tevékenység betöltése...</div>>
<Activity />
</Suspense>
</NamePromiseContext.Provider>
);
}
export default UserProfile;
3. Egyéni horog használata a párhuzamos lekérések kezeléséhez
Összetettebb forgatókönyvekhez, potenciálisan feltételes adatfüggőségekkel, létrehozhat egy egyéni horogot a párhuzamos adatlekérés kezeléséhez, és egy erőforrást adhat vissza, amelyet a Suspense használhat.
import { useState, useEffect, useRef } from 'react';
function useParallelData(fetchFunctions) {
const [resource, setResource] = useState(null);
const mounted = useRef(true);
useEffect(() => {
mounted.current = true;
const promises = fetchFunctions.map(fn => fn());
const suspender = Promise.all(promises).then(
(results) => {
if (mounted.current) {
setResource({ status: 'success', value: results });
}
},
(error) => {
if (mounted.current) {
setResource({ status: 'error', value: error });
}
}
);
setResource({
status: 'pending',
value: suspender,
});
return () => {
mounted.current = false;
};
}, [fetchFunctions]);
const read = () => {
if (!resource) {
throw new Error('Az erőforrás még nincs inicializálva');
}
if (resource.status === 'pending') {
throw resource.value;
}
if (resource.status === 'error') {
throw resource.value;
}
return resource.value;
};
return { read };
}
// Példa használatra:
async function fetchUserData(userId) {
// API-hívás szimulálása
await new Promise(resolve => setTimeout(resolve, 300));
return { id: userId, name: 'Felhasználó ' + userId };
}
async function fetchUserPosts(userId) {
// API-hívás szimulálása
await new Promise(resolve => setTimeout(resolve, 500));
return [{ id: 1, title: '1. bejegyzés' }, { id: 2, title: '2. bejegyzés' }];
}
function UserProfile({ userId }) {
const { read } = useParallelData([
() => fetchUserData(userId),
() => fetchUserPosts(userId),
]);
const [userData, userPosts] = read();
return (
<div>
<h2>{userData.name}</h2>
<ul>
{userPosts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
</div>
);
}
function App() {
return (
<Suspense fallback=<div>Felhasználói adatok betöltése...</div>>
<UserProfile userId={123} />
</Suspense>
);
}
export default App;
Ez a megközelítés magában foglalja az ígéretek és a betöltési állapotok kezelésének összetettségét a horgon belül, így a komponenskód tisztább és inkább az adatok megjelenítésére koncentrál.
4. Szelektív hidratáció a streaming szerver rendereléssel
A szerveren renderelt alkalmazásokhoz a React 18 szelektív hidratációt vezetett be a streaming szerver rendereléssel. Ez lehetővé teszi a HTML-t a kliensnek darabokban küldeni, ahogy az elérhetővé válik a szerveren. A lassú betöltésű komponenseket beburkolhatja a <Suspense> határokkal, lehetővé téve, hogy az oldal többi része interaktívvá váljon, miközben a lassú komponensek még mindig betöltődnek a szerveren. Ez drámaian javítja az érzékelt teljesítményt, különösen a lassú hálózati kapcsolattal vagy eszközökkel rendelkező felhasználók számára.
Vegyünk egy olyan helyzetet, ahol egy hírportáloknak a világ különböző régióiból (például Ázsia, Európa, Amerika) származó cikkeket kell megjelenítenie. Néhány adatforrás lassabb lehet, mint a többi. A szelektív hidratálás lehetővé teszi, hogy a cikkeket a gyorsabb régiókból először jelenítsék meg, miközben a lassabb régiókból származók még mindig betöltődnek, megakadályozva a teljes oldal letiltását.
Hibák és betöltési állapotok kezelése
Miközben a Suspense leegyszerűsíti a betöltési állapotok kezelését, a hibakezelés továbbra is kulcsfontosságú. A hibahatárok (a componentDidCatch életciklus metódus vagy a useErrorBoundary horog használatával az olyan könyvtárakból, mint a `react-error-boundary`) lehetővé teszik, hogy elegánsan kezelje a hibákat, amelyek az adatlekérés vagy a renderelés során fordulnak elő. Ezeket a hibahatárokat stratégiailag kell elhelyezni az adott Suspense határokon belüli hibák elkapásához, megakadályozva az egész alkalmazás összeomlását.
import React, { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
function MyComponent() {
// ... olyan adatok lekérése, amelyek hibát okozhatnak
}
function App() {
return (
<ErrorBoundary fallback={<div>Valami elromlott!</div>}>
<Suspense fallback={<div>Betöltés...</div>}>
<MyComponent />
</Suspense>
</ErrorBoundary>
);
}
Ne felejtse el informatív és felhasználóbarát tartalék felhasználói felületet biztosítani a betöltési és a hibaállapotokhoz egyaránt. Ez különösen fontos a nemzetközi felhasználók számára, akik lassabb hálózati sebességgel vagy regionális szolgáltatási kimaradásokat tapasztalhatnak.
Legjobb gyakorlatok az adatlekérés optimalizálásához a Suspense használatával
- Kritikus adatok azonosítása és rangsorolása: Határozza meg, mely adatok elengedhetetlenek az alkalmazás kezdeti rendereléséhez, és rangsorolja az adatok lekérését.
- Adatok előtöltése, amikor lehetséges: Használja a `React.preload` és az erőforrásokat az adatok előtöltéséhez, mielőtt a komponenseknek szüksége lenne rájuk, minimalizálva a betöltési állapotokat.
- Adatok egyidejű lekérése: Használja a `Promise.all`-t vagy az egyéni horgokat több adatlekérés párhuzamos elindításához.
- API végpontok optimalizálása: Győződjön meg arról, hogy az API végpontjai teljesítményre optimalizáltak, minimalizálva a késést és a terhelés méretét. Fontolja meg az olyan technikák használatát, mint a GraphQL, hogy csak a szükséges adatokat kérje le.
- Gyorsítótárazás megvalósítása: Gyorsítótárazza a gyakran elért adatokat az API-kérések számának csökkentése érdekében. Fontolja meg olyan könyvtárak használatát, mint az `swr` vagy a `react-query` a robusztus gyorsítótárazási képességekhez.
- Kódsplit használata: Ossza fel az alkalmazást kisebb darabokra a kezdeti betöltési idő csökkentése érdekében. Kombinálja a kódsplitet a Suspense-szel az alkalmazás különböző részeinek fokozatos betöltéséhez és rendereléséhez.
- Teljesítmény figyelése: Rendszeresen figyelje az alkalmazás teljesítményét olyan eszközökkel, mint a Lighthouse vagy a WebPageTest a teljesítmény szűk keresztmetszeteinek azonosításához és kezeléséhez.
- Hibák elegáns kezelése: Implementáljon hibahatárokat az adatlekérés és a renderelés során bekövetkező hibák elkapásához, informatív hibaüzeneteket biztosítva a felhasználók számára.
- Fontolja meg a szerveroldali renderelést (SSR): SEO és teljesítmény okokból fontolja meg az SSR használatát a streaminggel és a szelektív hidratálással a gyorsabb kezdeti élmény érdekében.
Következtetés
A React Suspense, a párhuzamos adatlekérés stratégiáival kombinálva, hatékony eszközkészletet biztosít a reszponzív és teljesítményt nyújtó webalkalmazások építéséhez. A vízesés probléma megértésével és az olyan technikák megvalósításával, mint az előtöltés, az egyidejű lekérés a Promise.all-lel és az egyéni horgokkal, jelentősen javíthatja a felhasználói élményt. Ne felejtse el elegánsan kezelni a hibákat, és figyelni a teljesítményt, hogy alkalmazása világszerte optimalizált maradjon a felhasználók számára. Ahogy a React folyamatosan fejlődik, az olyan új funkciók felfedezése, mint a szelektív hidratáció a streaming szerver rendereléssel, tovább fogja javítani a kivételes felhasználói élmények nyújtásának képességét, függetlenül a helytől vagy a hálózati körülményektől. Ezen technikák alkalmazásával olyan alkalmazásokat hozhat létre, amelyek nemcsak funkcionálisak, hanem a globális közönség számára is örömet okoznak a használatuk.
Ez a blogbejegyzés a React Suspense-szel végzett párhuzamos adatlekérési stratégiák átfogó áttekintését kívánta nyújtani. Reméljük, hogy informatívnak és hasznosnak találta. Javasoljuk, hogy kísérletezzen ezekkel a technikákkal saját projektjeiben, és ossza meg megállapításait a közösséggel.